﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Input;
using Foundation;
using Foundation.ExtensionMethods;
using Poet.Properties;
using Poet.Views;

namespace Poet.ViewModels
{
    /// <summary>
    /// Search view model.
    /// </summary>
    [DataContract]
    internal class SearchViewModel : ViewModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SearchViewModel"/> class.
        /// </summary>
        public SearchViewModel()
        {
            Initialize();
        }

        /// <summary>
        /// Gets the look in view model.
        /// </summary>
        public LookInViewModel<DocumentView> LookInViewModel { get; private set; }

        /// <summary>
        /// Gets the recent regex patterns.
        /// </summary>
        [DataMember]
        public ObservableCollection<string> RecentRegexPatterns { get; private set; }

        /// <summary>
        /// Gets the recent text patterns.
        /// </summary>
        [DataMember]
        public ObservableCollection<string> RecentTextPatterns { get; private set; }

        /// <summary>
        /// Gets the recent replace templates.
        /// </summary>
        [DataMember]
        public ObservableCollection<string> RecentReplaceTemplates { get; private set; }

        /// <summary>
        /// Gets or sets the regex options.
        /// </summary>
        /// <value>
        /// The regex options.
        /// </value>
        [DataMember]
        public RegexOptions RegexOptions
        {
            get { return Get(() => RegexOptions); }
            set { Set(() => RegexOptions, RegexOptions.InvertFlags(value)); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [match whole word].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [match whole word]; otherwise, <c>false</c>.
        /// </value>
        [DataMember]
        public bool WholeWord
        {
            get { return Get(() => WholeWord); }
            set { Set(() => WholeWord, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [use regex].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [use regex]; otherwise, <c>false</c>.
        /// </value>
        [DataMember]
        public bool UseRegex
        {
            get { return Get(() => UseRegex); }
            set { Set(() => UseRegex, value); }
        }

        /// <summary>
        /// Gets or sets the active document view.
        /// </summary>
        /// <value>
        /// The active document view.
        /// </value>
        public DocumentView ActiveDocumentView
        {
            get { return TrunkViewModel.ActiveDocumentView as DocumentView; }
            set
            {
                TrunkViewModel.ActiveDocumentView = value;
                RaisePropertyChangedEvent(() => ActiveDocumentView);
            }
        }

        /// <summary>
        /// Gets or sets the regex pattern.
        /// </summary>
        /// <value>
        /// The regex pattern.
        /// </value>
        [DataMember]
        public string RegexPattern
        {
            get { return Get(() => RegexPattern); }
            set { Set(() => RegexPattern, value); }
        }

        private Regex UpdateRegex(bool useCache)
        {
            var pattern =
                UseRegex
                    ? RegexPattern
                    : ToRegex(TextPattern);

            if (string.IsNullOrEmpty(pattern)) return null;
            if (WholeWord) pattern = @"\b" + pattern + @"\b";
            if (useCache && _regex != null && Equals(pattern, _regex.ToString()))
                return _regex;

            try
            {
                _regex = new Regex(pattern, RegexOptions | RegexOptions.Compiled);
                CurrentError = null;
                return _regex;
            }
            catch (Exception exception)
            {
                CurrentError = exception.Message;
                return null;
            }
        }

        private Regex _regex;

        public Regex Regex
        {
            get { return UpdateRegex(true); }
        }

        /// <summary>
        /// Gets or sets the text pattern.
        /// </summary>
        /// <value>
        /// The text pattern.
        /// </value>
        [DataMember]
        public string TextPattern
        {
            get { return Get(() => TextPattern); }
            set { Set(() => TextPattern, value); }
        }

        /// <summary>
        /// Gets or sets the replace template.
        /// </summary>
        /// <value>
        /// The replace template.
        /// </value>
        [DataMember]
        public string ReplaceTemplate
        {
            get { return Get(() => ReplaceTemplate); }
            set { Set(() => ReplaceTemplate, value); }
        }

        /// <summary>
        /// Gets the main view model.
        /// </summary>
        protected TrunkViewModel TrunkViewModel { get { return Store.OfType<TrunkViewModel>(); } }

        /// <summary>
        /// Gets the matches.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public List<Match> GetMatches(string text)
        {
            if (Regex == null || text == null) return null;
            var matches = Regex.Matches(text).Cast<Match>().ToList();
            if ((RegexOptions & RegexOptions.RightToLeft) == RegexOptions.RightToLeft)
                matches.Reverse();
            return matches;
        }

        [OnDeserialized]
        private void Initialize(StreamingContext context = default(StreamingContext))
        {
            LookInViewModel = new LookInViewModel<DocumentView>(TrunkViewModel.DocumentViews);
            if (TextPattern == null) TextPattern = string.Empty;
            if (RegexPattern == null) RegexPattern = string.Empty;
            if (ReplaceTemplate == null) ReplaceTemplate = string.Empty;
            if (RecentTextPatterns == null) RecentTextPatterns = new ObservableCollection<string>();
            if (RecentRegexPatterns == null) RecentRegexPatterns = new ObservableCollection<string>();
            if (RecentReplaceTemplates == null) RecentReplaceTemplates = new ObservableCollection<string>();
            CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, Executed, CanExecute));
            CommandBindings.Add(new CommandBinding(ApplicationCommands.Replace, Executed, CanExecute));
            ApplicationClosing += () => this.TrySerializeDataContract();

            this[() => RegexOptions].PropertyChanged += (sender, args) => UpdateRegex(false);
            this[() => UseRegex].PropertyChanged += (sender, args) => UpdateRegex(false);
            this[() => WholeWord].PropertyChanged += (sender, args) => UpdateRegex(false);

            TrunkViewModel.PropertyChanged += (o, e) =>
                {
                    var propertyName = GetPropertyName(() => TrunkViewModel.ActiveDocumentView);
                    if (e.PropertyName != propertyName) return;
                    LookInViewModel.ActiveItem = ActiveDocumentView;
                    RaisePropertyChangedEvent(() => ActiveDocumentView);
                };

            PropertyChanged += (sender, args) => GetDataErrorInfo(args.PropertyName);
        }

        private string CurrentError { get; set; }

        protected override string GetDataErrorInfo(string propertyName = null)
        {
            return CurrentError;
        }

        public void AddRecentPattern()
        {
            if (UseRegex)
                AddRecentItem(RecentRegexPatterns, RegexPattern, Settings.Default.RecentRegexPatternsCount);
            else AddRecentItem(RecentTextPatterns, TextPattern, Settings.Default.RecentTextPatternsCount);
            AddRecentItem(RecentReplaceTemplates, ReplaceTemplate, Settings.Default.RecentReplaceTemplatesCount);
        }

        private static void AddRecentItem(ObservableCollection<string> items, string item, int maxItems)
        {
            if (string.IsNullOrWhiteSpace(item))
                return;
            var index = items.IndexOf(item);
            if (index < 0)
            {
                items.Insert(0, item);
                items.Truncate(maxItems);
            }
            else if (index > 0) items.Move(index, 0);
        }

        private static string ToRegex(string text)
        {
            return text.
                Replace(@".", @"\.").
                Replace(@"$", @"\$").
                Replace(@"^", @"\^").
                Replace(@"{", @"\{").
                Replace(@"[", @"\[").
                Replace(@"(", @"\(").
                Replace(@"|", @"\|").
                Replace(@")", @"\)").
                Replace(@"*", @"\*").
                Replace(@"+", @"\+").
                Replace(@"?", @"\?").
                Replace(@"\", @"\\");
        }

        protected void CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var parameter = args.Parameter as string;
            var command = (RoutedCommand) args.Command;
            if (!LookInViewModel.SelectedItems.Contains(ActiveDocumentView))
                LookInViewModel.SelectedItems.Add(ActiveDocumentView);
            var items = LookInViewModel.Items.Where(i => LookInViewModel.SelectedItems.Contains(i)).ToList();

            if (args.Parameter.Equals("All"))
            {
                args.CanExecute =
                    CanExecuteOrExecute(ActiveDocumentView, command, items, true, false) ||
                    CanExecuteOrExecute(ActiveDocumentView, command, items, false, false);
                return;
            }

            if (!Equals(null, ActiveDocumentView))
                if (parameter == null || (!parameter.Contains("Back") && !parameter.Contains("Next")))
                {
                    args.CanExecute = command.CanExecute(parameter, ActiveDocumentView);
                    return;
                }

            if (parameter == null) return;
            var direct = parameter.Contains("Next");
            args.CanExecute = items.Any() && CanExecuteOrExecute(ActiveDocumentView, command, items, direct, false);
        }

        private void Executed(object sender, ExecutedRoutedEventArgs args)
        {
            if (args.Parameter == null) return;
            var parameter = args.Parameter.ToString();
            var command = (RoutedCommand) args.Command;
            var items = LookInViewModel.Items.Where(i => LookInViewModel.SelectedItems.Contains(i)).ToList();

            if (parameter.Contains("Single"))
            {
                command.Execute(parameter, ActiveDocumentView);
                ActiveDocumentView.Activate();
                return;
            }

            if (parameter.Equals("All"))
            {
                while (CanExecuteOrExecute(ActiveDocumentView, command, items, true, true)) { }
                while (CanExecuteOrExecute(ActiveDocumentView, command, items, false, true)) { }
                return;
            }

            if (parameter.Contains("All"))
                while (CanExecuteOrExecute(ActiveDocumentView, command, items, parameter.Contains("Next"), true)) { }
            else CanExecuteOrExecute(ActiveDocumentView, command, items, parameter.Contains("Next"), true);

            var pattern = UseRegex ? RegexPattern : TextPattern;
            var collection = UseRegex ? RecentRegexPatterns : RecentTextPatterns;
            var count =
                UseRegex
                    ? Settings.Default.RecentRegexPatternsCount
                    : Settings.Default.RecentTextPatternsCount;
            collection.AddAsRecent(pattern);
            collection.Truncate(count);

            if (UseRegex)
                RegexPattern = pattern;
            else TextPattern = pattern;

            if (args.Command == ApplicationCommands.Replace)
            {
                pattern = ReplaceTemplate;
                collection = RecentReplaceTemplates;
                count = Settings.Default.RecentTextPatternsCount;
                collection.AddAsRecent(pattern);
                collection.Truncate(count);
            }
        }

        protected bool CanExecuteOrExecute(DocumentView documentView, RoutedCommand command, IList<DocumentView> items,
                                           bool directSeek, bool executeIfCan)
        {
            if (documentView == null || (!string.IsNullOrEmpty(Error) && UseRegex))
                return false;

            var canExecute = command.CanExecute(directSeek, documentView);
            if (canExecute)
            {
                if (executeIfCan)
                {
                    command.Execute(directSeek, documentView);
                    documentView.Activate();
                }

                return true;
            }

            var isSide =
                directSeek
                    ? Equals(items.Last(), documentView)
                    : Equals(items.First(), documentView);

            if (items.Count <= 1 || isSide)
                return false;

            var index = items.IndexOf(documentView);
            var prevDocumentView =
                directSeek
                    ? items[index + 1]
                    : items[index - 1];

            return CanExecuteOrExecute(prevDocumentView, command, items, directSeek, executeIfCan);
        }
    }
}